from mmlang import *


class Modman:
    def __init__(self):
        self.__script_args = sys.argv
        self.languageFileSettingPath = os.path.join(g_script_dir, g_modman_language_file_name)
        self.__commandArgs = []

        # .modman 目录的父目录路径
        self.projectPath = self.getModmanDirectoryPath()
        self.projectPath = os.getcwd() if self.projectPath is False else self.projectPath
        # .modman 目录路径
        self.modmanPath = self.projectPath + os.path.sep + g_modman_dir_name
        self.workpath = os.getcwd()
        self.languagePackage = Language(fileGetContent(self.languageFileSettingPath).strip())

        self.__options = {
            "force": False,
            "copy": False,
            "absolute": False
        }

        self.__processingDefaultCommand()
        self.__processingArgsInput()
        self.__dispatchCommand(self.__command, self.__commandArgs)

    def langtext(self, text, *params):
        return self.languagePackage.text(text, *params)

    def rawlangtext(self, text):
        return self.languagePackage.rawtext(text)

    def __processingDefaultCommand(self):
        if len(self.__script_args) > 1:
            self.__command = self.__script_args[1]
            del self.__script_args[1]
        else:
            self.__command = 'help'

        del self.__script_args[0]

    # 处理参数输入
    def __processingArgsInput(self):
        args = []
        process_option = ''

        for i in range(len(self.__script_args)):
            arg = self.__script_args[i].strip()

            if arg[0] == '-':
                if process_option == '':
                    self.__commandArgs = args
                else:
                    self.__dispatchOption(process_option, args)

                args = []
                process_option = arg
            else:
                args.append(self.__script_args[i])

            if i == len(self.__script_args)-1:
                if process_option == '':
                    self.__commandArgs = args
                else:
                    self.__dispatchOption(process_option, args)

    def __dispatchOption(self, option, args):
        method_name = '_optionHandle_' + option.replace('_', '#')
        method_name = method_name.replace('-', '_')

        if hasattr(self, method_name):
            method = getattr(self, method_name)
            method(args)
        else:
            self.error(self.langtext('unrecognized_option', option))

    def __dispatchCommand(self, cmd, args):
        method_name = '_commandHandle_' + cmd.replace('_', '#')
        method_name = method_name.replace('-', '_')

        if hasattr(self, method_name):
            method = getattr(self, method_name)
            method(args)
        else:
            self.error(self.langtext('unrecognized_command', cmd), -2)


    def getModmanDirectoryPath(self):
        cur_path = os.getcwd()
        found = True

        while not os.path.isdir(cur_path + os.path.sep + g_modman_dir_name):
            cur_path = os.path.dirname(cur_path)

            if cur_path == '' or cur_path == os.path.dirname(cur_path):
                found = False
                break

        if found:
            return cur_path
        else:
            return False

    def getValidMods(self, wildcard='*'):
        mods = self.getModDirectorys(wildcard)
        valid_mods = []

        for mod in mods:
            modman_file = os.path.join(self.modmanPath, mod, g_modman_file_name)

            if os.path.isfile(modman_file):
                handle = open(modman_file)

                try:
                    content = handle.read()
                finally:
                    handle.close()

                if not content.strip() == "":
                    valid_mods.append(mod)

        return valid_mods

    def getModsMap(self, file):
        modsmap = dict()

        if os.path.isfile(file):
            try:
                f = open(file, "r")
            except Exception as e:
                self.warning(str(e))
                return modsmap

            for line in f:
                line = line.strip()

                if line == "":
                    continue

                line = re.sub(r'\s+', ' ', line) + ' '
                split_result = line.split(' ', 1)
                src = split_result[0].strip()
                dest = split_result[1].strip()
                dest = src if dest == "" else dest
                modsmap[src] = dest
            f.close()

        return modsmap

    def deployFile(self, srcpath, targetpath):
        is_dir = True if os.path.isdir(srcpath) else False
        parent_path = os.path.dirname(targetpath)
        project_visit_path = srcpath if self.isOptionEnabled('absolute') else relativepath(os.path.dirname(targetpath), srcpath)
        srcpath = standardpath(srcpath)
        targetpath = standardpath(targetpath)

        if not os.path.exists(parent_path):
            os.makedirs(parent_path)

        os.chdir(parent_path)

        if self.isOptionEnabled('copy'):
            if self.isOptionEnabled('force') and os.path.exists(targetpath):
                removeDirTreeOrFile(targetpath)

            if is_dir:
                shutil.copytree(srcpath, targetpath)
            else:
                shutil.copyfile(srcpath, targetpath)

            linksrc = targetpath
            linktarget = srcpath
        else:
            if os.path.islink(targetpath):
                linkval = os.readlink(targetpath)
                if not self.isOptionEnabled('force') and os.path.exists(linkval) and linkval == project_visit_path:
                    return True, False, '', '', ''
                else:
                    os.remove(targetpath)
            elif os.path.exists(targetpath):
                if self.isOptionEnabled('force'):
                    removeDirTreeOrFile(targetpath)
                else:
                    return False, True, self.langtext('file_or_folder_already_existing', targetpath), targetpath, project_visit_path

            linksrc = targetpath
            linktarget = project_visit_path

            if not os.path.exists(project_visit_path):
                return False, True, self.langtext('link_no_exists_file', srcpath), linksrc, linktarget
            else:
                os.symlink(project_visit_path, os.path.basename(targetpath), is_dir)

        os.chdir(self.workpath)
        return True, True, '', linksrc, linktarget

    def undeployFile(self, path):
        if not os.path.exists(path):
            return True, False, ''

        if os.path.islink(path):
            os.remove(path)
        else:
            if self.isOptionEnabled('copy'):
                removeDirTreeOrFile(path)
            else:
                return False, True, self.langtext('existing_file_or_folder' ,path)

        return True, True, ''

    def getModDirectorys(self, wildcard='*'):
        results = os.listdir(self.modmanPath)
        mods = []
        regx = wildcardtoregx(wildcard)

        for dir_name in results:
            fullpath = os.path.join(self.modmanPath,  dir_name)

            if os.path.isdir(fullpath) and re.match(regx, dir_name):
                mods.append(dir_name)

        return mods

    def getOption(self, option):
        try:
            return self.__options[option]
        except KeyError as e:
            return False

    def setOption(self, option, value):
        self.__options[option] = value
        return self

    def enableOption(self, option):
        return self.setOption(option, True)

    def disableOption(self, option):
        return self.setOption(option, False)

    def isOptionEnabled(self, option):
        return self.getOption(option) == True

    def removeEmptyDirTree(self, path, parentPath, printtext):
        if os.path.isdir(path) and not os.listdir(path) and not isSamePath(parentPath, path):

            if printtext != '':
                print(printtext.format(path))

            if g_system == 'windows':
                os.system('rd "' + path + '"')
            else:
                os.remove(path)

            self.removeEmptyDirTree(os.path.dirname(path), parentPath, printtext)

    def checkInit(self):
        if not os.path.isdir(self.modmanPath):
            self.error(self.langtext('no_init'))

    def error(self, msg, code=-1):
        print(self.langtext('output_msg_error', msg))
        exit(code)

    def warning(self, msg):
        print(self.langtext('output_msg_warning', msg))

    def quitwarning(self, msg):
        print(self.langtext('output_msg_warning', msg))
        exit()

    def quitmsg(self, msg):
        print(msg)
        exit()

# command handle
    def _commandHandle_pjpath(self, args):
        print(self.projectPath)

    def _commandHandle_mmpath(self, args):
        print(self.modmanPath)

    # modman [init, i]
    def _commandHandle_init(self, args):
        modman_dir_name = g_modman_dir_name

        if os.path.exists(modman_dir_name):
            if os.path.isfile(modman_dir_name):
                if self.isOptionEnabled('force'):
                    try:
                        os.remove(modman_dir_name)
                        os.makedirs(modman_dir_name)
                    except Exception as e:
                        self.error(str(e))
                else:
                    self.quitwarning(self.langtext('init_warning_file_exists',
                                                   os.path.join(os.getcwd(), modman_dir_name)))
            else:
                self.quitmsg(self.langtext('already_init'))
        else:
            try:
                os.makedirs(modman_dir_name)
            except Exception as e:
                self.error(str(e))

        self.quitmsg(self.langtext('init_success'))

    def _commandHandle_lang(self, args):
        print(self.languagePackage.lang)

    def _commandHandle_listlang(self, args):
        print("\n".join(self.languagePackage.getLangList()))

    def _commandHandle_setlang(self, args):
        self.checkInit()

        if len(args) == 0 or args[0].strip() == '':
            self.error(self.langtext('missing_lang_code'))

        lang = args[0].strip()
        result = self.languagePackage.setLang(lang)

        if result:
            filePutContent(self.languageFileSettingPath, self.languagePackage.lang)
            print(self.langtext('language_change', self.languagePackage.lang))
        else:
            self.error(self.langtext('language_no_exists', lang))

    def _commandHandle_i(self, args):
        self._commandHandle_init(args)

    # mdoman [list, l]
    def _commandHandle_list(self, args):
        self.checkInit()
        wildcard = '*' if len(args)==0 or args[0].strip()=='' else args[0]

        if self.isOptionEnabled('all'):
            mods = self.getModDirectorys(wildcard)
        else:
            mods = self.getValidMods(wildcard)

        print("\n".join(mods))

        if not self.isOptionEnabled('short'):
            modslen = len(mods)
            print(self.langtext('total_of_module', str(modslen)))

    def _commandHandle_l(self, args):
        self._commandHandle_list(args)

    # modman [clone, c]
    def _commandHandle_clone(self, args):
        self.checkInit()
        if len(args) == 0 or args[0].strip() == "":
            self.error(self.langtext('missing_clone_uri'))

        modname = os.path.basename(args[0])
        modname = re.sub(r'\.git$', '', modname, 0, re.IGNORECASE)
        modname_path = os.path.join(self.modmanPath, modname)

        if self.isOptionEnabled('force') and os.path.exists(modname_path):
            try:
                removeDirTreeOrFile(modname_path)
            except Exception as e:
                self.error(self.langtext('clone_error_remove_dir', str(e)))

        os.chdir(os.path.join(self.modmanPath))
        result = os.system('git clone ' + args[0].strip())

        if not result and not self.isOptionEnabled('nodeploy'):
            self.__dispatchCommand('deploy', [modname])

    def _commandHandle_c(self, args):
        self._commandHandle_clone(args)

    # modman [update, up]
    def _commandHandle_update(self, args):
        self.checkInit()
        if len(args) == 0 or args[0].strip() == "":
            self.error(self.langtext('missing_module_name'))

        mod_wilcard = args[0]
        valid_mods = self.getValidMods(mod_wilcard)
        modslen = len(valid_mods)

        if modslen > 1 and not self.isOptionEnabled('yes'):
            ynConfirm(self.langtext('module_update_confirm', str(modslen)))

        for mod in valid_mods:
            print(self.langtext('update_line', mod))
            modpath = os.path.join(self.modmanPath, mod)
            os.chdir(modpath)
            os.system('git pull')
            os.chdir(self.projectPath)

            self.__dispatchCommand('deploy', [mod])

    def _commandHandle_up(self, args):
        self._commandHandle_update(args)

    # modman [deploy, d]
    def _commandHandle_deploy(self, args):
        self.checkInit()

        if len(args) == 0 or args[0].strip() == "":
            self.error(self.langtext('missing_module_name'))

        mod_wilcard = args[0]
        valid_mods = self.getValidMods(mod_wilcard)
        modslen = len(valid_mods)
        total_new = 0
        total_item = 0
        warning = 0

        if modslen > 1 and not self.isOptionEnabled('yes'):
            ynConfirm(self.langtext('module_deploy_confirm', str(modslen)))

        for validmod in valid_mods:
            module_dir = os.path.join(self.modmanPath, validmod)
            module_map_file = os.path.join(module_dir, g_modman_file_name)
            modsmap = self.getModsMap(module_map_file)
            count = 0
            newcount = 0
            subwarning = 0
            print(self.langtext('deploying_prompt', validmod))

            for modpath in modsmap:
                count += 1
                modfullpath = standardpath(os.path.join(module_dir, modpath))
                targetfullpath = standardpath(os.path.join(self.projectPath, modsmap[modpath]))
                linksrc = ''
                linktarget = ''

                try:
                    (ok, applying, emsg, linksrc, linktarget) = self.deployFile(modfullpath, targetfullpath)
                except Exception as e:
                    applying = True
                    emsg = str(e)
                    ok = False

                if applying:
                    newcount += 1
                    print(self.langtext('applying_prompt',
                                        self.langtext('flag_ok') if ok else self.langtext('flag_fail'),
                                        linksrc,
                                        self.langtext('flag_copyfrom') if self.isOptionEnabled('copy') else self.langtext('flag_linkto'),
                                        linktarget))
                    if not ok:
                        warning += 1
                        subwarning += 1
                        self.warning(emsg)

            total_new += newcount
            total_item += count
            print(self.langtext('deploying_subtotal', str(count), str(newcount), str(subwarning)))

        print(self.langtext('deploying_grand_total', str(total_item), str(total_new), str(warning)))
        print(self.langtext('finish'))

    def _commandHandle_d(self, args):
        self._commandHandle_deploy(args)

    # modman [deploy-all, da, d-all]
    def _commandHandle_deploy_all(self, args):
        self.__dispatchCommand('deploy', ['*'])

    def _commandHandle_d_all(self, args):
        self._commandHandle_deploy_all(args)

    def _commandHandle_da(self, args):
        self._commandHandle_deploy_all(args)

    # modman [undeploy, ud]
    def _commandHandle_undeploy(self, args):
        self.checkInit()
        if len(args) == 0 or args[0].strip() == "":
            self.error(self.langtext('missing_module_name'))

        mod_wilcard = args[0]
        valid_mods = self.getValidMods(mod_wilcard)
        modslen = len(valid_mods)
        total_new = 0
        warning = 0

        if modslen > 1 and not self.isOptionEnabled('yes'):
            ynConfirm(self.langtext('module_undeploy_confirm', str(modslen)))

        for validmod in valid_mods:
            module_dir = os.path.join(self.modmanPath, validmod)
            module_map_file = os.path.join(module_dir, g_modman_file_name)
            modsmap = self.getModsMap(module_map_file)
            count = 0
            newcount = 0
            subwarning = 0
            print("undeploy '" + validmod + "'")

            for modpath in modsmap:
                count += 1
                targetfullpath = standardpath(os.path.join(self.projectPath, modsmap[modpath]))
                type = 'file'

                if os.path.islink(targetfullpath):
                    type = 'link'
                elif os.path.isdir(targetfullpath):
                    type = 'dir'

                try:
                    (ok, applying, emsg) = self.undeployFile(targetfullpath)
                except Exception as e:
                    applying = True
                    emsg = str(e)
                    ok = False

                if applying:
                    newcount += 1
                    total_new += 1
                    print(self.langtext('undeploy_removing_prompt', type, targetfullpath))

                    if not ok:
                        warning += 1
                        subwarning += 1
                        self.warning(emsg)

            print(self.langtext('undeploying_subtotal', str(newcount), str(subwarning)))

        print(self.langtext('undeploying_grand_total',str(total_new) ,str(warning)))
        print(self.langtext('finish'))

    def _commandHandle_ud(self, args):
        self._commandHandle_undeploy(args)

    # modman [clean]
    def _commandHandle_clean(self, args):
        self.checkInit()
        total = 0
        warning = 0
        emptydirs = []
        wordpath = os.getcwd()

        if len(args) > 0:
            wordpath = args[0]

        if not os.path.isdir(wordpath):
            self.error(self.langtext('clean_path_no_exists', wordpath))

        wordpath = os.path.realpath(wordpath)

        if not isSubDir(self.projectPath, wordpath) and not isSamePath(self.projectPath, wordpath):
            self.error(self.langtext('clean_path_out_of_range', wordpath))

        for root, dirs, files in os.walk(wordpath):
            for name in (dirs+files):
                entity_path = os.path.join(root, name)

                if os.path.islink(entity_path):
                    linkval = os.readlink(entity_path)
                    linkentity_path = os.path.join(os.path.dirname(entity_path), linkval)

                    if not os.path.exists(linkentity_path):
                        try:
                            total += 1
                            if self.isOptionEnabled('list'):
                                print(entity_path)
                            else:
                                os.remove(entity_path)
                                print(self.langtext('clean_remove_link', entity_path, linkval))

                        except Exception as e:
                            warning += 1
                            self.warning(str(e))

                # 空目录标记
                if self.isOptionEnabled('emptydir'):
                    if os.path.isdir(entity_path):
                        if not os.listdir(entity_path):
                            emptydirs.append(entity_path)

                    parent_path = os.path.dirname(entity_path)

                    if not os.listdir(parent_path):
                        emptydirs.append(parent_path)

        if self.isOptionEnabled('emptydir'):
            for dirpath in emptydirs:
                if not os.path.exists(dirpath):
                    continue

                if isSubDir(self.modmanPath, dirpath) or isSamePath(self.modmanPath, dirpath) :
                    continue

                if self.projectPath == dirpath:
                    continue

                try:
                    total += 1
                    if self.isOptionEnabled('list'):
                        print(dirpath)
                    else:
                        self.removeEmptyDirTree(dirpath, wordpath, self.rawlangtext('clean_remove_empty_dir'))
                except Exception as e:
                    warning += 1
                    self.warning(str(e))

        if not self.isOptionEnabled('short'):
            print(self.langtext('clean_grand_total', str(total), str(warning)))

            if not self.isOptionEnabled('list'):
                print(self.langtext('finish'))

    # modman [help, -help, --help, h, -h, --h]
    def _commandHandle_help(self, args):
        print(self.langtext('app_title', g_version))
        print(self.langtext('help'))

    def _commandHandle__help(self, args):
        self._commandHandle_help(args)

    def _commandHandle___help(self, args):
        self._commandHandle_help(args)

    def _commandHandle_h(self, args):
        self._commandHandle_help(args)

    def _commandHandle__h(self, args):
        self._commandHandle_help(args)

    def _commandHandle___h(self, args):
        self._commandHandle_help(args)

    # modman [version, v, -v, --v, --version, -version]
    def _commandHandle_version(self, args):

        if self.isOptionEnabled('short'):
            self.quitmsg(g_version)
        else:
            self.quitmsg(self.langtext('version', g_version))

    def _commandHandle__version(self, args):
        self._commandHandle_version(args)

    def _commandHandle___version(self, args):
        self._commandHandle_version(args)

    def _commandHandle_v(self, args):
        self._commandHandle_version(args)

    def _commandHandle__v(self, args):
        self._commandHandle_version(args)

    def _commandHandle___v(self, args):
        self._commandHandle_version(args)

    # modman [remove, rm]
    def _commandHandle_remove(self, args):
        self.checkInit()
        if len(args) == 0 or args[0].strip() == "":
            self.error(self.langtext('missing_module_name'))

        mod_wilcard = args[0]
        valid_mods = self.getModDirectorys(mod_wilcard)
        modslen = len(valid_mods)

        if self.isOptionEnabled('undeploy'):
            self.enableOption('force').enableOption('copy')

        if modslen > 1 and not self.isOptionEnabled('yes'):
            ynConfirm(self.langtext('module_remove_confirm', str(modslen)))

        for validmod in valid_mods:
            print(self.langtext('remove_prompt', validmod))

            if self.isOptionEnabled('undeploy'):
                self.__dispatchCommand('undeploy', [validmod])

            removeDirTreeOrFile(os.path.join(self.modmanPath, validmod))

    def _commandHandle_rm(self, args):
        self._commandHandle_remove(args)


# option handle
    def _optionHandle___force(self, args):
        self.enableOption('force')

    def _optionHandle__f(self, args):
        self._optionHandle___force(args)

    def _optionHandle___copy(self, args):
        self.enableOption('copy')

    def _optionHandle___absolute(self, args):
        self.enableOption('absolute')

    def _optionHandle___abs(self, args):
        self._optionHandle___absolute(args)

    def _optionHandle___yes(self, args):
        self.enableOption('yes')

    def _optionHandle__y(self, args):
        self._optionHandle___yes(args)

    def _optionHandle___short(self, args):
        self.enableOption('short')

    def _optionHandle__s(self, args):
        self._optionHandle___short(args)

    def _optionHandle___undeploy(self, args):
        self.enableOption('undeploy')

    def _optionHandle__ud(self, args):
        self._optionHandle___undeploy(args)

    def _optionHandle__a(self, args):
        self._optionHandle___all(args)

    def _optionHandle___all(self, args):
        self.enableOption('all')

    def _optionHandle___nodeploy(self, args):
        self.enableOption('nodeploy')

    def _optionHandle__nd(self, args):
        self._optionHandle___nodeploy(args)

    def _optionHandle___emptydir(self, args):
        self.enableOption('emptydir')

    def _optionHandle__ed(self, args):
        self._optionHandle___emptydir(args)

    def _optionHandle___list(self, args):
        self.enableOption('list')


Modman()